package com.ht.api.business.ad;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.*;

import javax.sql.DataSource;

import cn.hutool.json.JSONUtil;
import com.ht.api.db.*;
import com.ht.api.db.task.HtTaskExecutor;
import com.ht.api.task.ad.ReadyAdTaskGroupTask;
import com.ht.api.util.NoticeUtil;
import com.ht.api.util.RuleSqlUtil;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import lombok.extern.slf4j.Slf4j;

/**
 * 广告计划相关支撑类
 * @author asq
 * @createTime 2025年3月1日 13:43:30
 */
@Slf4j
public class PlanHelper {
    /**
     * 根据应用范围和人群范围和规则条件寻找到符合条件的用户ID
     * @param audienceRule
     * @param conditionRule
     * @return
     */
    public static String findOneIdsByAppAndRule (DataSource ds, JSONObject appRule, JSONArray audienceRule, JSONObject conditionRule) {

        // 构建规则sql
        String ruleSql = null;
        List<Object> paramList = new ArrayList<>();
        if (audienceRule != null || conditionRule != null) {
            ruleSql = buildAudienceAndConditionSql(audienceRule, conditionRule, paramList, false);
        }

        StringBuilder appSql = buildAppSql(appRule);

        // 根据app范围和规则范围，组装查询oneid的sql
        StringBuilder sql = new StringBuilder();
        if (ruleSql == null && appSql == null) {
            sql.append("select bitmap_to_string(bitmap_union(to_bitmap(oneid))) oneid from user_main where status = 0");
        } else {
            if (ruleSql == null) {
                sql.append("select bitmap_to_string(t.oneid) oneid from (" + appSql.toString() + ") t ");
            } else if (appSql == null) {
                sql.append("select bitmap_to_string(t.oneid) oneid from (" + ruleSql.toString() + ") t ");
            } else {
                sql.append("select bitmap_to_string(bitmap_and(t1.oneid, t2.oneid)) from ").append("("+ruleSql.toString()+") t1,").append("("+appSql.toString()+") t2 ");
            }
        }

        String ids = Db.val(ds, sql.toString(), paramList);

        return ids;
    }

    public static StringBuilder buildAppSql (JSONObject appRule) {

        return buildAppSql(appRule, null);
    }

    public static StringBuilder buildAppSql (JSONObject appRule, String oneid) {
        // 构建app范围sql
        StringBuilder appSql = null;
        if (appRule != null && !appRule.isNull("wwxkflx")) {
            appSql = StrUtil.builder();
            JSONObject wwx = appRule.getJSONObject("wwxkflx");

            appSql.append("select bitmap_union(to_bitmap(oneid)) oneid from user_origin where oneid is not null ");

            if (StrUtil.isNotBlank(oneid)) {
                appSql.append("and oneid = ").append(oneid).append(" ");
            }

            if (!wwx.isNull("direct")) {
                appSql.append("and appid in ("+wwx.getJSONArray("direct").join(",")+") ");
            }

            if (!wwx.isNull("touch")) {
                JSONObject wuser = wwx.getByPath("touch.wuser", JSONObject.class);
                if (wuser != null) {
                    if (!wuser.isNull("group")) {
                        appSql.append("and touch_id in (select userid from wx_workuser group_id in ("+wuser.getJSONArray("group_id").join(",")+")) ");
                    }
                    if (!wuser.isNull("direct")) {
                        appSql.append("and touch_id in ("+wuser.getJSONArray("direct").join(",")+") ");
                    }
                }

            }
        }

        return appSql;
    }

    /**
     * 组装人群和规则条件的组合sql
     * @param audienceRule
     * @param conditionRule
     * @param paramList
     * @return
     */
    public static String buildAudienceAndConditionSql (JSONArray audienceRule, JSONObject conditionRule, List<Object> paramList, boolean isIdString) {

        String func = "bitmap_union";

        if (isIdString) {
            func = "bitmap_to_string";
        }

        String audiencesql = "";
        if (audienceRule != null && !audienceRule.isEmpty()) {
            audiencesql = "select bitmap_union(to_bitmap(oneid)) oneid from audience_relation where status = 0 and audience_id in ( "
                    + audienceRule.join(",") + ") ";
        }

        String conditionSql = "";
        if (conditionRule != null && !conditionRule.isEmpty()) {
            conditionSql = RuleSqlUtil.buildConditionSql(conditionRule, paramList);//构建条件sql
        }

        StringBuilder sql = StrUtil.builder();
        if (StrUtil.isNotBlank(audiencesql) && StrUtil.isNotBlank(conditionSql)) {
            sql.append("select "+func+"(bitmap_and(audienceuser.oneid, conditionuser.id)) oneid from ");
            sql.append("(").append(audiencesql).append(") audienceuser, ");
            sql.append("("+conditionSql+") conditionuser ");
        } else if (StrUtil.isNotBlank(audiencesql) && StrUtil.isBlank(conditionSql)){
            sql.append("select "+func+"(t.oneid) oneid from (" + audiencesql + ") t ");
        } else if (StrUtil.isBlank(audiencesql) && StrUtil.isNotBlank(conditionSql)){
            sql.append("select "+func+"(t.id) oneid from (" + conditionSql + ") t ");
        } else {
            sql.append("select " + func + "(bitmap_union(to_bitmap(oneid))) oneid from user_main where status = 0");
        }

        return sql.toString();
    }

    /**
     * 生成计划
     * @param param
     * @return
     */
    public static Entity generatePlan(JSONObject param) {
        Entity adPlan = Entity.create("ad_plan");
        adPlan.set("name", param.get("name"));
        adPlan.set("campaign_id", param.get("campaign_id"));
        adPlan.set("source_id", param.get("source_id"));
        adPlan.set("source_type", param.get("source_type"));
        adPlan.set("user_refresh_type", param.get("user_refresh_type"));
        adPlan.set("cycle_type", param.get("cycle_type"));
        adPlan.set("fix_time", param.get("fix_time"));
        adPlan.set("priority", param.get("priority"));
        adPlan.set("audience_rule", param.get("audience_rule"));
        adPlan.set("app_rule", param.get("app_rule"));
        adPlan.set("condition", param.get("condition"));
        adPlan.set("status", 1);

        param.set("plan_id", adPlan.getMainId());


        return adPlan;
    }

    /**
     * 生成策略
     * @param param
     * @return
     */
    public static Entity generateCampaign(JSONObject param) {
        Entity adCampaign = Entity.create("ad_campaign");
        adCampaign.set("name", param.get("name"));
        adCampaign.set("exec_type", param.get("exec_type"));

        param.set("campaign_id", adCampaign.getMainId());

        return adCampaign;
    }

    /**
     * 生成策略广告
     * @param param
     * @return
     */
    public static Entity createCampaignItem(JSONObject param) {
        Entity adCampaignItem = Entity.create("ad_campaign_item");
        adCampaignItem.set("campaign_id", param.get("campaign_id"));
        adCampaignItem.set("ad_id", param.get("ad_id"));
        adCampaignItem.set("package_id", param.get("package_id"));
        adCampaignItem.set("ad_tags", param.get("ad_tags"));
        adCampaignItem.set("ad_position", param.get("ad_position"));
        adCampaignItem.set("time_type", param.get("time_type"));
        adCampaignItem.set("fix_time", param.get("fix_time"));
        adCampaignItem.set("delay_value", param.get("delay_value"));
        adCampaignItem.set("slot_time", param.get("slot_time"));
        return adCampaignItem;
    }

    /**
     * 创建广告计划用户
     * @param ids
     * @param param
     * @return
     */
    public static List<Entity> createPlanUser(String ids, JSONObject param) {
        List<Entity> planUserList = new ArrayList<>();

        if (StrUtil.isNotBlank(ids)) {
            String eventTime = DateUtil.now();

            for (String id : ids.split(",")) {
                Entity planUser = Entity.create("ad_plan_user");
                planUser.set("oneid", id);
                planUser.set("source_id", param.get("source_id"));
                planUser.set("source_type", param.get("source_type"));
                planUser.set("plan_id", param.get("plan_id"));
                planUser.set("first_in_time", eventTime);
                planUser.set("last_in_time", eventTime);
                planUser.set("push_days", 1);
                planUser.set("status", 1);

                planUserList.add(planUser);

                Entity adPlanUserLogs = Entity.create("ad_plan_user_logs");
                adPlanUserLogs.set("oneid", id);
                adPlanUserLogs.set("plan_id", param.get("plan_id"));
                adPlanUserLogs.set("time", eventTime);
                adPlanUserLogs.set("status", 1);

                planUserList.add(adPlanUserLogs);
            }
        }

        return planUserList;
    }

    /**
     * 根据广告计划用户和广告位，寻找当前应安排的策略广告
     * @param planUser
     * @param position
     * @param limitCount
     * @return
     */
    public static DbList findCampaignItemsByUser (DataSource ds, Entity planUser, String position, int limitCount) {
        String inTime = planUser.getStr("first_in_time");
        String oneid = planUser.getStr("oneid");
        String planId = planUser.getStr("plan_id");
        String campaignId = planUser.getStr("campaign_id");

        int delayDays = getDaysBetween(inTime);

        String sql = """
                SELECT
                	*
                FROM
                	ad_campaign_item
                WHERE
                	campaign_id = ?
                	AND ad_position = ?
                	AND time_type = ?
                	AND delay_value <= ?
                	AND !bitmap_contains ((SELECT bitmap_union (plan_ads) FROM ad_plan_user WHERE plan_id = ? AND oneid = ?), main_id)
                ORDER BY
                	delay_value ASC
                	LIMIT ?
                """;

        DbList campaignItems = Db.query(ds, sql, campaignId, position, "SCHEDULE", delayDays, planId, oneid, limitCount);

        return campaignItems;
    }

    /**
     * 根据策略配置项创建广告任务
     * @param planUser
     * @param campaignItems
     * @return
     */
    public static List<Entity> createAdTaskToUserByCampaignItem(long tenantId, DataSource ds, Entity plan, Entity planUser, DbList campaignItems) {

        List<Entity> taskList = new ArrayList<>();

        String oneid = planUser.getStr("oneid");

        JSONObject appRule = plan.getJSONObject("app_rule");

        Map<String, Entity> userMap = new HashMap<>();

        for (Entity campaignItem : campaignItems) {
            Entity creative = findCreativeByCampaignItem(ds, campaignItem, oneid);

            if (creative == null) {
                continue;
            }

            // 根据应用范围和oneid，寻找可以推送广告任务的触点关系
            String adPosition = campaignItem.getStr("ad_position");
            Entity touchUser = userMap.get(adPosition);
            if (touchUser == null) {
                touchUser = PlanHelper.findTouchUser(tenantId, oneid, adPosition, appRule);
                userMap.put(adPosition, touchUser);
            }

            Entity adUserTask = buildAdTaskToUser(plan, touchUser, campaignItem, creative);
            taskList.add(adUserTask);
        }

        return taskList;
    }

    /**
     * 创建广告任务
     * @param plan
     * @param touchUser
     * @param campaignItem
     * @param creative
     * @return
     */
    public static Entity buildAdTaskToUser (Entity plan, Entity touchUser, Entity campaignItem, Entity creative) {
        Entity adUserTask = Entity.create("ad_user_task");
        adUserTask.getMainId();
        adUserTask.set("origin_id", touchUser.get("main_id"));
        adUserTask.set("openid", touchUser.get("openid"));
        adUserTask.set("appid", touchUser.get("appid"));
        adUserTask.set("touch", touchUser.get("touch"));
        adUserTask.set("touch_id", touchUser.get("touch_id"));
        adUserTask.set("plan_id", plan.getMainId());
        adUserTask.set("campaign_id", campaignItem.get("campaign_id"));
        adUserTask.set("campaign_item_id", campaignItem.getMainId());
        adUserTask.set("ad_id", creative.get("ad_id"));
        adUserTask.set("ad_type", creative.get("ad_type"));
        adUserTask.set("creative_id", creative.getMainId());
        adUserTask.set("creative_content", creative.get("copy"));
        adUserTask.set("creative_material", creative.get("material"));
        adUserTask.set("priority", plan.get("priority"));
        adUserTask.set("ad_position", campaignItem.get("ad_position"));
        adUserTask.set("status", 1);

        JSONObject context = JSONUtil.createObj();
        context.set("openid", adUserTask.get("openid"));
        context.set("ad_id", adUserTask.get("ad_id"));

        if (adUserTask.getInt("touch") == 1) {
            context.set("userid", adUserTask.get("touch_id"));
        } else if (adUserTask.getInt("touch") == 2) {
            context.set("chat_id", adUserTask.get("touch_id"));
        }

        context.set("plan_id", adUserTask.get("plan_id"));
        context.set("creative_id", adUserTask.get("creative_id"));

        adUserTask.set("context", context);

        String planTime = plan.getStr("fix_time");

        // 计算广告投放的计划时间，主要是用到运营手动创建的时候
        if (StrUtil.isNotBlank(planTime)) {
            String timeType = campaignItem.getStr("time_type");
            if (timeType.equals("DELAYMINUTES")) {
                int minutes = campaignItem.getInt("delay_value");
                String planDay = DateUtil.format(DateUtil.offsetMinute(DateUtil.parse(planTime), minutes), "yyyy-MM-dd");
                String slotTime = DateUtil.format(DateUtil.offsetMinute(DateUtil.parse(planTime), minutes), "HH:mm");

                adUserTask.set("plan_day", planDay);
                adUserTask.set("slot_time", slotTime);
            } else if (timeType.equals("DELAYDAYS")) {

                int days = campaignItem.isNull("delay_value") ? 0 : campaignItem.getInt("delay_value");
                String planDay = DateUtil.format(DateUtil.offsetDay(DateUtil.parse(planTime), days), "yyyy-MM-dd");

                adUserTask.set("plan_day", planDay);
                adUserTask.set("slot_time", campaignItem.get("slot_time"));
            } else if (timeType.equals("IMMEDIATE")) {
                String planDay = DateUtil.format(DateUtil.parse(planTime), "yyyy-MM-dd");
                String slotTime = DateUtil.format(DateUtil.parse(planTime), "HH:mm");

                adUserTask.set("plan_day", planDay);
                adUserTask.set("slot_time", slotTime);
            }

        } else {
            adUserTask.set("plan_day", DateUtil.today());
            adUserTask.set("slot_time", campaignItem.get("slot_time"));
        }

        return adUserTask;
    }

    /**
     * 通过策略配置项，找到可推送的广告创意
     * @param campaignItem
     * @param oneid
     * @return
     */
    public static Entity findCreativeByCampaignItem (DataSource ds, Entity campaignItem, String oneid) {
        String adId = campaignItem.getStr("ad_id");
        String packageId = campaignItem.getStr("package_id");
        JSONArray adTags = campaignItem.getJSONArray("ad_tags");

        Entity creative = null;
        if (StrUtil.isNotBlank(adId)) {
            String sql = """
                            SELECT c.main_id, c.copy, c.ad_id, a.ad_type 
                            FROM creative c 
                            LEFT JOIN ad a ON a.main_id = c.ad_id
                            WHERE c.ad_id = ? 
                            AND !bitmap_contains ((SELECT bitmap_union (plan_ads) FROM ad_push_count WHERE oneid = ?), c.main_id) 
                            AND c.status = 1
                            ORDER BY c.order_num ASC
                            LIMIT 1
                            """;
            creative = Db.findOne(ds, sql, adId, oneid);

        } else if (StrUtil.isNotBlank(packageId)) {
            String sql = """
                            SELECT c.main_id, c.copy, c.ad_id, a.ad_type 
                            FROM ad_package_relation p 
                            LEFT JOIN creative c ON p.ad_id = c.ad_id 
                            LEFT JOIN ad a ON a.main_id = c.ad_id
                            WHERE p.package_id = ? 
                             AND !bitmap_contains ((SELECT bitmap_union (plan_ads) FROM ad_push_count WHERE oneid = ?), c.main_id) 
                            """;

            if (adTags != null && !adTags.isEmpty()) {
                sql += "AND EXISTS ( " +
                        "SELECT 1 " +
                        "FROM tag_relation " +
                        "WHERE BITMAP_CONTAINS(relation_id, c.ad_id) " +
                        "AND tag_id IN ("+adTags.join(",") +
                        "))";
            }

            sql += """
                    AND c.status = 1
                            ORDER BY c.order_num ASC, p.order_num ASC LIMIT 1
                    """;

            creative = Db.findOne(ds, sql, packageId, oneid);

        }

        return creative;
    }

    /**
     * 寻找触点关系
     * @param oneid
     * @param position
     * @param apps
     * @return
     */
    public static Entity findTouchUser(long tenantId, String oneid, String position, JSONObject apps) {
        DataSource ds = DbConfig.useTenantDb(tenantId);

        String sql = buildAppSql(tenantId, apps, position, oneid);

        return Db.findOne(ds, sql);
    }


    public static DbList findTouchUserList(long tenantId, String planId, JSONObject apps, String position) {
        DataSource ds = DbConfig.useTenantDb(tenantId);

        StringBuilder sql = new StringBuilder();
        sql.append(buildAppSql(tenantId, apps, position, null));

        sql.append(" and bitmap_contains((select bitmap_union(to_bitmap(oneid)) from ad_plan_user where plan_id = "+planId+" and status = 1), oneid)");

        DbList list = Db.query(ds, sql.toString());

        log.info(sql.toString());

        log.info("list " + list.size());

        Set<String> users = new HashSet<>();
        DbList resultList = DbList.create();
        for (Entity entity : list) {
            String openid = entity.getStr("openid");
            if (!users.contains(openid)) {
                resultList.add(entity);
                users.add(openid);
            }
        }

        log.info("resultList " + resultList.size());

        return resultList;
    }

    public static String buildAppSql (long tenantId, JSONObject apps, String position, String oneid) {
        int touch = 1;

        StringBuilder sql = StrUtil.builder();
        sql.append("select main_id,openid,appid,touch,touch_id,oneid,moment_partition from user_origin where oneid is not null and touch = " + touch + " ");

        if (StrUtil.isNotBlank(oneid)) {
            sql.append("and oneid = " + oneid + " ");
        }

        if (apps != null && !apps.isNull("wwxkflx")) {
            JSONObject wwx = apps.getJSONObject("wwxkflx");
            if (!wwx.isNull("direct")) {
                sql.append("and appid in ("+wwx.getJSONArray("direct").join(",")+") ");
            }

            if (!wwx.isNull("touch")) {
                JSONObject wuser = wwx.getByPath("touch.wuser", JSONObject.class);
                if (wuser != null) {
                    if (!wuser.isNull("group")) {
                        sql.append("and touch_id in (select userid from wx_workuser group_id in ("+wuser.getJSONArray("group_id").join(",")+")) ");
                    }
                    if (!wuser.isNull("direct")) {
                        sql.append("and touch_id in ("+wuser.getJSONArray("direct").join(",")+") ");
                    }
                }

            }
        }

        return sql.toString();
    }


    /**
     * 根据广告任务，对任务进行分组，并创建相应的广告任务组，以及启动广告任务组的准备任务
     * @param dbList
     */
    public static void createAdTaskGroup (long tenantId, List<Entity> dbList, String planId) {
        DataSource ds = DbConfig.useTenantDb(tenantId);
        StreamLoadConfig config = DbConfig.useTenantSLC(tenantId);

        List<Entity> dataList = new ArrayList<>();

        Map<String, List<Entity>> groupMap = new HashMap<>();
        for (Entity task : dbList) {
            String adPosition = task.getStr("ad_position");

            String groupKey = "";
            if (!adPosition.equals("moment") && !adPosition.equals("private_chat")) { // 如果不是朋友圈和群发消息，则不用对任务进行分组处理
                dataList.add(task);
                continue;
            } else if (adPosition.equals("moment")) {
                groupKey = task.getStr("appid") + task.getStr("ad_position") + task.getStr("touch")
                        + task.getStr("creative_id") + task.getStr("plan_day") + task.getStr("slot_time") + task.getStr("moment_partition");
            } else if (adPosition.equals("private_chat")) {
                groupKey = task.getStr("appid") + task.getStr("ad_position") + task.getStr("touch") + task.getStr("touch_id")
                        + task.getStr("creative_id") + task.getStr("plan_day") + task.getStr("slot_time") ;
            }

            if (!groupMap.containsKey(groupKey)) {
                List<Entity> taskList = new ArrayList<>();
                groupMap.put(groupKey, taskList);
            }

            List<Entity> taskList = groupMap.get(groupKey);
            taskList.add(task);
        }


        // 如果groupMap有数据，则说明有对广告任务进行分组，那么则需要对分组的数据进行处理
        List<Entity> taskGroupList = new ArrayList<>();
        for (String key : groupMap.keySet()) {
            List<Entity> taskList = groupMap.get(key);
            buildGroupTask (taskList, taskGroupList, dataList, planId);
        }

        // 任务组有数据，则新增
        if (taskGroupList.size() > 0) {
            dataList.addAll(taskGroupList);
        }

        Db.insertBatchWithStream(config, dataList);

        // 任务组有数据，则启动任务组的准备任务
        for (Entity taskGroup : taskGroupList) {

            JSONObject taskParam = JSONUtil.createObj();
            taskParam.set("appid", taskGroup.getStr("appid"));
            taskParam.set("tenant_id", tenantId);
            taskParam.set("ad_task_group_id", taskGroup.getMainId());
            HtTaskExecutor.submitTask(taskParam, new ReadyAdTaskGroupTask());
        }
    }


    /**
     * 构建广告组任务
     * @param taskList
     * @param taskGroupList
     * @param dataList
     */
    public static void buildGroupTask (List<Entity> taskList, List<Entity> taskGroupList, List<Entity> dataList, String planId) {
        Entity firstTask = taskList.get(0);
        String adPosition = firstTask.getStr("ad_position");

        String momentPartition = null;
        int groupSize = 10000;
        if (adPosition.equals("moment")) {
            momentPartition = firstTask.getStr("moment_partition");
            groupSize = 5000;
        }

        List<JSONObject> groupList = groupByTouchId(taskList, groupSize);

        for (JSONObject group : groupList) {
            JSONArray touchIds = group.getJSONArray("touch_id");
            JSONArray tasks = group.getJSONArray("records");

            Entity taskGroup = Entity.create("ad_task_group");
            String taskGroupId = taskGroup.getMainId();

            JSONArray customers = JSONUtil.createArray();
            for (int i = 0; i < tasks.size(); i++) {
                Entity task = Entity.create("ad_user_task");
                task.putAll(tasks.getJSONObject(i));
                task.set("ad_task_group_id", taskGroupId);

                dataList.add(task);

                JSONObject customer = JSONUtil.createObj();
                customer.set("openid", task.getStr("openid"));
                customer.set("touch_id", task.getStr("touch_id"));
                customers.add(customer);

                if (i == 0) {
                    taskGroup.set("ad_position", task.get("ad_position"));
                    taskGroup.set("ad_id", task.get("ad_id"));
                    taskGroup.set("context", task.get("context"));
                    taskGroup.set("creative_id", task.get("creative_id"));
                    taskGroup.set("creative_content", task.get("creative_content"));
                    taskGroup.set("touch", task.get("touch"));
                    taskGroup.set("touch_count", touchIds.size());
                    taskGroup.set("customer_count", tasks.size());
                    taskGroup.set("plan_time", task.getStr("plan_day") + " " + task.getStr("slot_time"));
                    taskGroup.set("appid", task.get("appid"));
                    taskGroup.set("status", 1);
                    taskGroup.set("plan_id", planId);

                    taskGroup.set("business_content", momentPartition);

                    taskGroupList.add(taskGroup);
                }
            }

            for (int i = 0; i < touchIds.size(); i++) {
                JSONObject user = touchIds.getJSONObject(i);
                Entity momentUser = Entity.create(adPosition.equals("moment") ? "wx_moment_user" : "wx_msg_user");
                momentUser.set("ad_task_group_id", taskGroup.getMainId());
                momentUser.set("userid", user.get("touch_id"));
                momentUser.set("customer_count", user.get("count"));
                momentUser.set("publish_status", 0);
                dataList.add(momentUser);
            }

            for (int i = 0; i < customers.size(); i++) {
                JSONObject customer = customers.getJSONObject(i);
                Entity momentCustomer = Entity.create(adPosition.equals("moment") ? "wx_moment_customer" : "wx_msg_customer");
                momentCustomer.set("ad_task_group_id", taskGroup.getMainId());
                momentCustomer.set("userid", customer.get("touch_id"));
                momentCustomer.set("openid", customer.get("openid"));
                momentCustomer.set("status", 0);
                dataList.add(momentCustomer);
            }
        }
    }


    /**
     * 按照指定大小分组
     * @param dataList 原始数据列表
     * @param groupSize 每组大小
     * @return 分组后的结果列表
     */
    public static List<JSONObject> groupByTouchId(List<Entity> dataList, int groupSize) {
        // 1. 首先按touchId分组
        Map<String, List<JSONObject>> touchMap = new HashMap<>();

        for (Entity data : dataList) {
            String touchId = data.getStr("touch_id");
            touchMap.computeIfAbsent(touchId, k -> new ArrayList<>())
                    .add(data);
        }

        // 2. 计算需要多少组
        int maxGroups = 0;
        for (List<JSONObject> records : touchMap.values()) {
            maxGroups = Math.max(maxGroups,
                    (records.size() + groupSize - 1) / groupSize);
        }

        // 3. 创建结果列表
        List<JSONObject> result = new ArrayList<>();
        for (int i = 0; i < maxGroups; i++) {
            result.add(new JSONObject());
        }

        // 4. 分配数据到各组
        for (Map.Entry<String, List<JSONObject>> entry : touchMap.entrySet()) {
            String touchId = entry.getKey();
            List<JSONObject> records = entry.getValue();

            // 对每个touchId的记录按组大小分组
            for (int i = 0; i < records.size(); i += groupSize) {
                int end = Math.min(i + groupSize, records.size());
                List<JSONObject> groupRecords = records.subList(i, end);

                int groupIndex = i / groupSize;
                JSONObject groupResult = result.get(groupIndex);

                // 添加touch_id信息
                JSONArray touchIds = groupResult.getJSONArray("touch_id");
                if (touchIds == null) {
                    touchIds = new JSONArray();
                    groupResult.put("touch_id", touchIds);
                }

                // 创建touch_id统计对象
                JSONObject touchIdInfo = new JSONObject();
                touchIdInfo.put("touch_id", touchId);
                touchIdInfo.put("count", groupRecords.size());
                touchIds.add(touchIdInfo);

                // 添加records
                JSONArray recordsArray = groupResult.getJSONArray("records");
                if (recordsArray == null) {
                    recordsArray = new JSONArray();
                    groupResult.put("records", recordsArray);
                }
                recordsArray.addAll(groupRecords);
            }
        }

        return result;
    }

    public static void checkPlanStatus (DataSource ds, StreamLoadConfig config, String planId) {
        Entity plan = Db.createSqlParser("ad_plan").select("cycle_type").where("main_id", planId).findOne(ds);

        if (!plan.getStr("cycle_type").equals("PERMANENT")) {
            Long count = Db.createSqlParser("ad_task_group").where("plan_id", planId).where("status", "!=", "5").count(ds);

            if (count != null && count == 0) {
                plan.set("main_id", planId);
                plan.set("status", 4);
                plan.set("end_time", DateUtil.now());
                Db.insert(ds, plan);

                DbList userList = Db.createSqlParser("ad_plan_user").select("main_id").where("plan_id", planId).query(ds);
                for (Entity entity : userList) {
                    entity.set("status", 3);
                }

                Db.insertBatchWithStream(config, userList);

                if (!plan.isNull("sys_creator")) {
                    NoticeUtil.send("广告计划["+plan.getStr("name")+"]已全部完成执行。", 1, plan.getStr("sys_creator"));
                }
            }
        }

    }

    private static int getDaysBetween(String timeStr) {
        try {
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
            LocalDateTime pastTime = LocalDateTime.parse(timeStr, formatter);
            LocalDateTime now = LocalDateTime.now();

            // 转换为日期（去掉时分秒）后再计算差值
            LocalDate pastDate = pastTime.toLocalDate();
            LocalDate nowDate = now.toLocalDate();

            return (int) ChronoUnit.DAYS.between(pastDate, nowDate);
        } catch (Exception e) {
            // 根据你的需求处理异常
            // 可以返回-1表示错误，或者抛出异常
            return -1;
        }
    }

}
